package com.zzyy.study.config;

import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import io.lettuce.core.AbstractRedisClient;
import io.lettuce.core.RedisClient;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.util.concurrent.TimeUnit;

/**
 * @auther zzyy
 * @create 2024-01-13 17:54
 */

@Configuration
@Slf4j
public class RedisConfig
{
    /**
     * redis序列化的工具配置类，下面这个请一定开启配置
     * 127.0.0.1:6379> keys *
     * 1) "ord:102"  序列化过
     * 2) "\xac\xed\x00\x05t\x00\aord:102"   野生，没有序列化过
     * this.redisTemplate.opsForValue(); //提供了操作string类型的所有方法
     * this.redisTemplate.opsForList(); // 提供了操作list类型的所有方法
     * this.redisTemplate.opsForSet(); //提供了操作set的所有方法
     * this.redisTemplate.opsForHash(); //提供了操作hash表的所有方法
     * this.redisTemplate.opsForZSet(); //提供了操作zset的所有方法
     * @param redisConnectionFactor
     * @return
     */
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactor)
    {
        RedisTemplate<String,Object> redisTemplate = new RedisTemplate<>();

        redisTemplate.setConnectionFactory(redisConnectionFactor);
        //设置key序列化方式string
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        //设置value的序列化方式json，使用GenericJackson2JsonRedisSerializer替换默认序列化
        redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());

        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());

        redisTemplate.afterPropertiesSet();

        return redisTemplate;
    }

    /**
     * 对本地缓存初始化
     * @return
     */
    @Bean
    public Cache<String,Object> localCache()
    {
        return Caffeine.newBuilder()
                //初始数量
                .initialCapacity(100)
                //最大条数
                .maximumSize(1000)
                //PS：expireAfterWrite和expireAfterAccess同时存在时，以expireAfterWrite为准。
                //最后一次写操作后经过指定时间过期
                .expireAfterWrite(5, TimeUnit.MINUTES)
                //最后一次读或写操作后经过指定时间过期
                //.expireAfterAccess(1, TimeUnit.SECONDS)
                //监听缓存被移除
                //.removalListener((key, val, removalCause) -> {})
                //记录命中
                .recordStats()
                .build();
    }

    /**
     * 远程缓存Redis客户端链接
     * @param lettuceConnectionFactory
     * @return
     */
    @Bean
    public RedisClient redisClient(@Autowired LettuceConnectionFactory lettuceConnectionFactory)
    {
        return (RedisClient)lettuceConnectionFactory.getNativeClient();
    }

    /**
     * 我们自定义的缓存控制器，有它来制衡远程缓存redis和本地缓存localCacheCaffeine
     * @param redisClient
     * @param localCacheCaffeine
     * @return
     */
    @Bean
    public CacheFrontendContext cacheFrontendContext(@Autowired RedisClient redisClient,@Autowired Cache localCacheCaffeine)
    {
        return new CacheFrontendContext(redisClient,localCacheCaffeine);
    }

    /**
     * CommandLineRunner接口解释：
     * Spring Boot的CommandLineRunner接口是一个函数式接口，用于在Spring Boot应用程序启动后执行一些初始化操作。
     * 它提供了一个run方法，该方法在应用程序启动后被调用。使用CommandLineRunner接口，可以在应用程序启动后执行
     * 一些必要的初始化操作，例如加载配置文件、初始化数据库连接、创建默认数据等。
     * 可以通过实现CommandLineRunner接口并重写run方法来定义自己的初始化逻辑。
     *
     * 本方法作用是完成redis+本地缓存的断线重连
     * @param cacheFrontendContext
     * @return
     */
    @Bean
    public CommandLineRunner init(@Autowired CacheFrontendContext cacheFrontendContext)
    {
        return new CommandLineRunner()
        {
            @Override
            public void run(String... args) throws Exception
            {
                while(true)
                {
                    cacheFrontendContext.check();
                    TimeUnit.SECONDS.sleep(3);
                }
            }
        };
    }
}

